import cgi
import wsgiref.handlers
import urlparse, urllib
import os
import logging
import datetime
import Cookie
import pprint
import pickle
import hashlib

from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext import db
from google.appengine.ext.webapp import template

from mymetadata import fetchers
from mymetadata.consumer.consumer import Consumer
from mymetadata.consumer import discover
from fetcher import UrlfetchFetcher
import store

_DEBUG = False


############## Models ###################
class Person(db.Model):
  mymetadata = db.StringProperty()
  date = db.DateTimeProperty(auto_now_add=True)
  hashedkey = db.StringProperty()

  def pretty_mymetadata(self):
    return self.mymetadata.replace('http://','').replace('https://','').rstrip('/').split('#')[0]
  
  def put(self):
    if self.hashedkey is None:
      if self.is_saved():
        key = self.key()
      else:
        key = db.Model.put(self)
    
      self.hashedkey = hashlib.sha1(str(key)).hexdigest()
  
    assert self.hashedkey
    return db.Model.put(self)
     

class Session(db.Expando):
  # the logged in person
  person = db.ReferenceProperty(Person)
  
  # Mymetadata library session stuff
  mymetadata_stuff = db.TextProperty()

  # when someone tries to demand a site and they aren't logged in,
  # we store it here
  url = db.StringProperty()
  
  # this goes in the cookie
  session_id = db.StringProperty()

  def put(self):
    if self.session_id is None:

      if self.is_saved():
        key = self.key() 
      else:
        key = db.Expando.put(self)
      
      self.session_id = hashlib.sha1(str(key)).hexdigest()
    else:
      key = self.key()

    assert self.session_id
    db.Expando.put(self)
    return key


class Website(db.Model):
  domain = db.StringProperty()
  name = db.StringProperty()
  votes = db.IntegerProperty()
  date = db.DateTimeProperty(auto_now_add=True)
  status = db.BooleanProperty()

class WebsiteVote(db.Model):
  website = db.ReferenceProperty(Website)
  voter = db.ReferenceProperty(Person)
  date = db.DateTimeProperty(auto_now_add=True)

############# Utils #####################
def bookmarklet(host, person):
  js = """
javascript:
var d=document,w=window,f='http://%s/add',l=d.location,e=encodeURIComponent,p='?bookmarklet=true&v=1&k=%s&url='+e(l.href),u=f+p;var a=function(){if(!w.open(u,'t','toolbar=0,resizable=0,status=1,width=250,height=150'))l.href=u;};if(/Firefox/.test(navigator.userAgent))setTimeout(a,0);else a();void(0)
""" % (host,person.hashedkey)
  return js.replace("\n",'')


############# Base Handler ##############
class BaseHandler(webapp.RequestHandler):
  session = None
  logged_in_person = None
  
  def get_store(self):
    return store.DatastoreStore()

  def args_to_dict(self):
    req = self.request
    return dict([(arg, req.get(arg)) for arg in req.arguments()])

  def has_session(self):
    return bool(self.session)

  def get_session_id_from_cookie(self):
    if self.request.headers.has_key('Cookie'):
      cookie = Cookie.SimpleCookie(self.request.headers['Cookie'])

      if cookie.has_key('session_id'):
        return cookie['session_id'].value

    return None

  def write_session_id_cookie(self, session_id):
    expires = datetime.datetime.now() + datetime.timedelta(weeks=2)
    expires_rfc822 = expires.strftime('%a, %d %b %Y %H:%M:%S +0000')
    self.response.headers.add_header(
      'Set-Cookie', 'session_id=%s; path=/; expires=%s' % (str(session_id), expires_rfc822))

  def get_session(self, create=True):
    if self.session:
      return self.session
    
    # get existing session
    session_id = self.get_session_id_from_cookie()
    if session_id:
      sessions = Session.gql("WHERE session_id = :1", session_id)
      if sessions.count() == 1:
        self.session = sessions[0]
        return self.session

    if create:
      self.session = Session()
      self.session.put()
      self.write_session_id_cookie(self.session.session_id)
      return self.session

    return None

  def get_logged_in_person(self):
    if self.logged_in_person:
      return self.logged_in_person

    s = self.get_session(create=False)
    if s and s.person:
      self.logged_in_person = s.person
      return self.logged_in_person

    return None

  def render(self, template_name, extra_values={}):
    values = {
      'request': self.request,
      'debug': self.request.get('deb'),
      'lip': self.get_logged_in_person()
      }
    
    if values['lip']:
      values['bookmarklet'] = bookmarklet(self.request.host, values['lip'])
    else:
      values['bookmarklet'] = None
    
    values.update(extra_values)
    cwd = os.path.dirname(__file__)
    path = os.path.join(cwd, 'templates', template_name + '.html')
    logging.debug(path)
    self.response.out.write(template.render(path, values, debug=_DEBUG))


  def report_error(self, message):
    """Shows an error HTML page.

    Args:
    message: string
    A detailed error message.
    """
    args = pprint.pformat(self.args_to_dict())
    self.render('error', vars())
    logging.error(message)

  def show_main_page(self, error_msg=None):
    """Do an internal (non-302) redirect to the front page.
    
    Preserves the user agent's requested URL.
    """
    page = MainPage()
    page.request = self.request
    page.response = self.response
    page.get(error_msg)



############## Handlers #################
class MainPage(BaseHandler):
  def get(self, error_msg=None):
    top_websites = Website.gql("ORDER BY votes DESC LIMIT 10")
    latest_votes = WebsiteVote.gql("ORDER BY date DESC LIMIT 7")

    people_count = Person.all().count()
    website_count = Website.all().count()

    template_values = {
      'top_websites': top_websites,
      'latest_votes': latest_votes,
      'error_msg': error_msg,
      'people_count': people_count,
      'website_count': website_count,
      }

    self.response.headers['X-XRDS-Location'] = 'http://'+self.request.host+'/rpxrds'
    self.render('main', template_values)

class TopDemandsPage(BaseHandler):
  def get(self):
    n = 20
    page = self.request.get('page')
    try:
      page = int(page)
    except:
      page = 1

    offset = (page * n) - n

    websites = Website.gql("ORDER BY votes DESC LIMIT %d OFFSET %d" % (n, offset))
    websites = list(websites)

    prev = None
    next = None
    if page > 1:
      prev = page - 1

    if len(websites) == n:
      next = page + 1
      
    template_values = {
      'websites': websites,
      'prev': prev,
      'next':next
      }

    self.render('top', template_values)


class DomainPage(BaseHandler):
  def get(self):
    parts = urlparse.urlparse(self.request.uri)
    path = parts[2]
    path_parts = path.split('/')
    domain = None
    if len(path_parts) >= 3:
      domain = path_parts[2]
      logging.info(domain)
      domain = domain.replace('%253A',':')

    w = None
    websites = Website.gql("WHERE domain = :1", domain)
    if websites.count() == 0:
      self.show_main_page("Don't know about "+cgi.escape(str(domain)))
      return 
    else:
      w = websites[0]
      
    votes = WebsiteVote.gql("WHERE website = :1 ORDER BY date ASC",w)
    votes = list(votes)
    c = int(len(votes)/3) + 1
    votes = list(votes)
    votes1 = votes[:c]
    votes2 = votes[c:c*2]
    votes3 = votes[c*2:]

    p = self.get_logged_in_person()
    lip_not_voted = True
    if p:
      lip_votes = WebsiteVote.gql("WHERE website = :1 AND voter = :2",w,p)
      if lip_votes.count() > 0:
        lip_not_voted = False

    template_values = {
      'from': cgi.escape(self.request.get('from')),
      'from_name': cgi.escape(self.request.get('from_name')),
      'website': w,
      'votes': votes,
      'lip_not_voted': lip_not_voted,
      'votes1': votes1,
      'votes2': votes2,
      'votes3': votes3,
      }

    self.render('domain', template_values)

class PersonPage(BaseHandler):
  def get(self):
    mymetadata = self.request.get('mymetadata')

    persons = Person.gql("WHERE mymetadata = :1",mymetadata)
    try:
      p = persons[0]
    except IndexError:
      self.show_main_page('Unknown user')
      return

    votes = WebsiteVote.gql("WHERE voter = :1 ORDER BY date DESC",p)
    votes = list(votes)
    c = int(len(votes)/2) + 1
    votes = list(votes)
    votes_1 = votes[:c]
    votes_2 = votes[c:]


    lip = self.get_logged_in_person()
      
    self.render('person',{'person':p,'votes':votes,
                          'lip_is_person': lip and lip.mymetadata == p.mymetadata,
                          'votes1':votes_1, 'votes2': votes_2})

class HomePage(BaseHandler):
  def get(self):
    if self.get_logged_in_person():
      self.render('home',{})
    else:
      self.redirect('/')

class AddWebsiteSubmit(BaseHandler):
  
  def vote_for(self, url, voter):
    if not voter:
      return None, "Don't know who you are"

    if not url or url.strip() == '':
      return None, 'No URL given'

    domain = urlparse.urlparse(url)[1]
    if domain.startswith('www.'):
      domain = domain[4:]
      
    if domain == self.request.host:
      return None, 'It works, now try it at other websites!'

    if not domain or domain.strip() == '':
      return None, 'No URL given'
    
    websites = Website.gql("WHERE domain = :1", domain)

    if websites.count() > 0:
      w = websites[0]

    else:
      w = Website()
      w.votes = 0
      w.domain = domain
      w.name = domain.split('.')[0]
      w.status = False
      w.put()

    def make_vote(website, voter):
      wv = WebsiteVote.gql("WHERE website = :1 AND voter = :2",website,voter)
      if wv.count() == 0:
        wv = WebsiteVote(website=website,voter=voter)
        wv.put()
    
    db.run_in_transaction(make_vote, w, voter)
    w.votes = WebsiteVote.gql("WHERE website = :1",w).count()
    w.put()

    return (w, 'Demand submitted.')

  def get(self):
    url = self.request.get('url')
    if not url:
      self.show_main_page('Bad URL')
      return

    from_bookmarklet = self.request.get('bookmarklet') == 'true'
    voter = None
    if from_bookmarklet:
      voters = Person.gql("WHERE hashedkey = :1", self.request.get('k'))
      if voters.count() == 1:
        voter = voters[0]
    else:
      voter = self.get_logged_in_person()

    if voter:
      website, msg = self.vote_for(url, voter)
    else:
      # don't know the user, save the url for after they log in
      s = self.get_session()
      s.url = url
      s.put()
      
      if from_bookmarklet:
        website = None
        msg = 'Who are you?'
      else:
        self.redirect('/login')
        return
    
    if from_bookmarklet:
      self.render('bookmarklet', {'msg':msg, 'website':website})
    else:
      self.redirect('/site/'+cgi.escape(website.domain))

class DeleteVote(BaseHandler):
  def get(self):
    domain = self.request.get('k')
    website = Website.gql("WHERE domain = :1", domain)[0]
    wv = WebsiteVote.gql("WHERE website = :1 AND voter = :2", website, self.get_logged_in_person())[0]
    website = wv.website  
    wv.delete()
    
    website.votes = WebsiteVote.gql("WHERE website = :1",website).count()
    if website.votes == 0:
      website.delete()
    
    self.redirect('/voter?mymetadata='+cgi.escape(self.get_logged_in_person().mymetadata))

class LoginPage(BaseHandler):
  def get(self):
    self.render('login',{})

class MymetadataStartSubmit(BaseHandler):
  def post(self):
    mymetadata = self.request.get('mymetadata_identifier')
    if not mymetadata:
      self.show_main_page()
      return
    
    c = Consumer({},self.get_store())
    try:
      auth_request = c.begin(mymetadata)
    except discover.DiscoveryFailure, e:
      logging.error('Error with begin on '+mymetadata)
      logging.error(str(e))
      self.show_main_page('An error occured determining your server information.  Please try again.')
      return

    parts = list(urlparse.urlparse(self.request.uri))
    parts[2] = 'mymetadata-finish'
    parts[4] = ''
    parts[5] = ''
    return_to = urlparse.urlunparse(parts)
      
    realm = urlparse.urlunparse(parts[0:2] + [''] * 4)

    # save the session stuff
    session = self.get_session()
    session.mymetadata_stuff = pickle.dumps(c.session)
    session.put()

    # send the redirect!  we use a meta because appengine bombs out
    # sometimes with long redirect urls
    redirect_url = auth_request.redirectURL(realm, return_to)
    self.response.out.write("<html><head><meta http-equiv=\"refresh\" content=\"0;url=%s\"></head><body></body></html>" % (redirect_url,))


class MymetadataFinish(BaseHandler):
  def get(self):
    args = self.args_to_dict()
    url = 'http://'+self.request.host+'/mymetadata-finish'
    
    session = self.get_session()
    s = {}
    if session.mymetadata_stuff:
      try:
        s = pickle.loads(str(session.mymetadata_stuff))
      except:
        session.mymetadata_stuff = None

    session.put()
        
    c = Consumer(s, self.get_store())
    auth_response = c.complete(args, url)
    
    if auth_response.status == 'success':
      mymetadata = auth_response.getDisplayIdentifier()
      persons = Person.gql('WHERE mymetadata = :1', mymetadata)
      if persons.count() == 0:
        p = Person()
        p.mymetadata = mymetadata
        p.put()
      else:
        p = persons[0]
      
      s = self.get_session()
      s.person = p.key()
      self.logged_in_person = p
      
      if s.url:
        add = AddWebsiteSubmit()
        add.request = self.request
        add.response = self.response
        add.vote_for(s.url, p)
        s.url = None
        
      s.put()

      self.redirect('/home')

    else:
      self.show_main_page('Mymetadata verification failed :(')

class LogoutSubmit(BaseHandler):
  def get(self):
    s = self.get_session()
    if s:
      s.person = None
      s.put()

    self.redirect('/')

class RssFeed(BaseHandler):
  def get(self):
    websites = Website.gql("ORDER BY date DESC LIMIT 100")
    self.response.headers.add_header('Content-Type','application/rss+xml')
    self.render('rss',{'websites':websites})

class AboutPage(BaseHandler):
  def get(self):
    self.render('about',{})

class RelyingPartyXRDS(BaseHandler):
  def get(self):
    xrds = """
<?xml version='1.0' encoding='UTF-8'?>
<xrds:XRDS
  xmlns:xrds='xri://$xrds'
  xmlns:mymetadata='http://mymetadata.net/xmlns/1.0'
  xmlns='xri://$xrd*($v*2.0)'>                                                                          
  <XRD>
    <Service>
      <Type>http://specs.mymetadata.net/auth/2.0/return_to</Type>
      <URI>http://%s/mymetadata-finish</URI>
    </Service>
</XRD>
</xrds:XRDS>      
""" % (self.request.host,)

    self.response.headers['Content-Type'] = 'application/xrds+xml'
    self.response.out.write(xrds)

class GoogleAppsCode(BaseHandler):
  def get(self):
    
    self.response.headers['Content-Type'] = 'text/html'
    self.response.out.write('googleffffffff9674d535')


########### Ship it! ###################################
def main():
  fetchers.setDefaultFetcher(UrlfetchFetcher()) 
  application = webapp.WSGIApplication(
                                       [('/', MainPage),
                                        (r'/site/.*', DomainPage),
                                        ('/voter', PersonPage),
                                        ('/home',HomePage),
                                        ('/rss', RssFeed),
                                        ('/top', TopDemandsPage),
                                        ('/add', AddWebsiteSubmit),
                                        ('/del', DeleteVote),
                                        ('/login', LoginPage),
                                        ('/logout', LogoutSubmit),
                                        ('/mymetadata-start', MymetadataStartSubmit),
                                        ('/mymetadata-finish', MymetadataFinish),
                                        ('/about', AboutPage),
                                        ('/rpxrds', RelyingPartyXRDS),
                                        ('/googlehostedservice.html', GoogleAppsCode)
                                        ],
                                       debug=True)
  wsgiref.handlers.CGIHandler().run(application)

if __name__ == "__main__":
  main()
